"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasUploadErrors = exports.uploadFolder = exports.getFilesByType = void 0;
const path_1 = __importDefault(require("path"));
const p_queue_1 = __importDefault(require("p-queue"));
const handleFieldsJS_1 = require("./handleFieldsJS");
const fileMapper_1 = require("../fileMapper");
const fileMapper_2 = require("../../api/fileMapper");
const modules_1 = require("../../utils/cms/modules");
const escapeRegExp_1 = require("../escapeRegExp");
const path_2 = require("../path");
const errors_1 = require("../../errors");
const logger_1 = require("../logger");
const files_1 = require("../../constants/files");
const lang_1 = require("../../utils/lang");
const HubSpotHttpError_1 = require("../../models/HubSpotHttpError");
const i18nKey = 'lib.cms.uploadFolder';
const queue = new p_queue_1.default({
    concurrency: 10,
});
function getFileType(filePath) {
    const extension = (0, path_2.getExt)(filePath);
    const moduleFolder = (0, modules_1.isModuleFolderChild)({ path: filePath, isLocal: true });
    if (moduleFolder)
        return files_1.FILE_TYPES.module;
    switch (extension) {
        case 'js':
        case 'css':
            return files_1.FILE_TYPES.cssAndJs;
        case 'html':
            return files_1.FILE_TYPES.template;
        case 'json':
            return files_1.FILE_TYPES.json;
        default:
            return files_1.FILE_TYPES.other;
    }
}
function isMetaJsonFile(filePath) {
    return path_1.default.basename(filePath).toLowerCase() === 'meta.json';
}
function resolveUploadPath(file, fieldsJsPaths, tmpDirRegex, regex, dest) {
    const fieldsJsFileInfo = fieldsJsPaths.find(f => f.outputPath === file);
    const relativePath = file.replace(fieldsJsFileInfo ? tmpDirRegex : regex, '');
    const destPath = (0, path_2.convertToUnixPath)(path_1.default.join(dest, relativePath));
    const originalFilePath = fieldsJsFileInfo ? fieldsJsFileInfo.filePath : file;
    return { fieldsJsFileInfo, relativePath, destPath, originalFilePath };
}
async function getFilesByType(filePaths, projectDir, rootWriteDir, commandOptions) {
    const { convertFields, fieldOptions } = commandOptions;
    const projectDirRegex = new RegExp(`^${(0, escapeRegExp_1.escapeRegExp)(projectDir)}`);
    const fieldsJsObjects = [];
    // Create object with key-value pairs of form { FileType.type: [] }
    const filePathsByType = Object.values(files_1.FILE_TYPES).reduce((acc, fileType) => {
        return {
            ...acc,
            [fileType]: [],
        };
    }, {});
    for (const filePath of filePaths) {
        const fileType = getFileType(filePath);
        const relPath = filePath.replace(projectDirRegex, '');
        if (!convertFields) {
            filePathsByType[fileType].push(filePath);
            continue;
        }
        const convertableFields = (0, handleFieldsJS_1.isConvertableFieldJs)(projectDir, filePath, convertFields);
        if (convertableFields) {
            const rootOrModule = path_1.default.dirname(relPath) === '/' ? files_1.FILE_TYPES.json : files_1.FILE_TYPES.module;
            const fieldsJs = await new handleFieldsJS_1.FieldsJs(projectDir, filePath, rootWriteDir, fieldOptions).init();
            /*
             * A fields.js will be rejected if the promise is rejected or if the some other error occurs.
             * We handle this gracefully by not adding the failed fields.js to the object list.
             */
            if (fieldsJs.rejected)
                continue;
            fieldsJsObjects.push(fieldsJs);
            filePathsByType[rootOrModule].push(fieldsJs.outputPath || '');
        }
        else {
            filePathsByType[fileType].push(filePath);
        }
    }
    return [filePathsByType, fieldsJsObjects];
}
exports.getFilesByType = getFilesByType;
const defaultUploadAttemptCallback = (file, destPath) => logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.uploadFolder.attempt`, {
    file: file || '',
    destPath,
}));
const defaultUploadSuccessCallback = (file, destPath) => logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.uploadFolder.success`, {
    file: file || '',
    destPath,
}));
const defaultUploadFirstErrorCallback = (file, destPath, error) => {
    logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.uploadFolder.failed`, { file, destPath }));
    if ((0, errors_1.isHubSpotHttpError)(error)) {
        logger_1.logger.debug(error.data);
    }
    else if (error instanceof Error) {
        logger_1.logger.debug(error.message);
    }
};
const defaultUploadRetryCallback = (file, destPath) => logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.uploadFolder.retry`, { file, destPath }));
const defaultUploadFinalErrorCallback = (accountId, file, destPath, error) => {
    const retryFailed = (0, lang_1.i18n)(`${i18nKey}.uploadFolder.retryFailed`, {
        file,
        destPath,
    });
    logger_1.logger.debug(retryFailed);
    throw new HubSpotHttpError_1.HubSpotHttpError(retryFailed, { cause: error }, {
        accountId,
        request: destPath,
        payload: file,
    });
};
async function uploadMetaJsonFiles(moduleFiles, uploadFile) {
    const moduleMetaJsonFiles = moduleFiles.filter(isMetaJsonFile);
    if (moduleMetaJsonFiles.length > 0) {
        await queue.addAll(moduleMetaJsonFiles.map(uploadFile));
    }
}
async function uploadFolder(accountId, src, dest, fileMapperOptions, commandOptions = {}, filePaths = [], cmsPublishMode = null) {
    const { saveOutput, convertFields, onAttemptCallback, onSuccessCallback, onFirstErrorCallback, onRetryCallback, onFinalErrorCallback, } = commandOptions;
    const _onAttemptCallback = onAttemptCallback || defaultUploadAttemptCallback;
    const _onSuccessCallback = onSuccessCallback || defaultUploadSuccessCallback;
    const _onFirstErrorCallback = onFirstErrorCallback || defaultUploadFirstErrorCallback;
    const _onRetryCallback = onRetryCallback || defaultUploadRetryCallback;
    const _onFinalErrorCallback = onFinalErrorCallback || defaultUploadFinalErrorCallback;
    const tmpDir = convertFields
        ? (0, handleFieldsJS_1.createTmpDirSync)('hubspot-temp-fieldsjs-output-')
        : null;
    const regex = new RegExp(`^${(0, escapeRegExp_1.escapeRegExp)(src)}`);
    const apiOptions = (0, fileMapper_1.getFileMapperQueryValues)(cmsPublishMode, fileMapperOptions);
    const failures = [];
    let fieldsJsPaths = [];
    const tmpDirRegex = new RegExp(`^${(0, escapeRegExp_1.escapeRegExp)(tmpDir || '')}`);
    const [filesByType, fieldsJsObjects] = await getFilesByType(filePaths, src, tmpDir, commandOptions);
    if (fieldsJsObjects.length) {
        fieldsJsPaths = fieldsJsObjects.map(fieldsJs => {
            return { outputPath: fieldsJs.outputPath, filePath: fieldsJs.filePath };
        });
    }
    function uploadFile(file) {
        const { originalFilePath, destPath } = resolveUploadPath(file, fieldsJsPaths, tmpDirRegex, regex, dest);
        return async () => {
            _onAttemptCallback(originalFilePath, destPath);
            try {
                await (0, fileMapper_2.upload)(accountId, file, destPath, apiOptions);
                _onSuccessCallback(originalFilePath, destPath);
            }
            catch (err) {
                if ((0, errors_1.isAuthError)(err)) {
                    throw err;
                }
                _onFirstErrorCallback(file, destPath, err);
                failures.push({
                    file,
                    destPath,
                });
            }
        };
    }
    // Upload all meta.json files first
    await uploadMetaJsonFiles(filesByType[files_1.FILE_TYPES.module] || [], uploadFile);
    // Collect all remaining files for upload
    const deferredFiles = [];
    Object.entries(filesByType).forEach(([fileType, files]) => {
        if (fileType === files_1.FILE_TYPES.module) {
            // Add non-meta.json module files
            deferredFiles.push(...files.filter(f => !isMetaJsonFile(f)));
        }
        else {
            // Add all non-module files
            deferredFiles.push(...files);
        }
    });
    // Upload all remaining files concurrently
    if (deferredFiles.length > 0) {
        await queue.addAll(deferredFiles.map(uploadFile));
    }
    const results = await queue
        .addAll(failures.map(({ file, destPath }) => {
        return async () => {
            _onRetryCallback(file, destPath);
            try {
                await (0, fileMapper_2.upload)(accountId, file, destPath, apiOptions);
                _onSuccessCallback(file, destPath);
                return {
                    resultType: files_1.FILE_UPLOAD_RESULT_TYPES.SUCCESS,
                    error: null,
                    file,
                };
            }
            catch (error) {
                if ((0, errors_1.isAuthError)(error)) {
                    throw error;
                }
                _onFinalErrorCallback(accountId, file, destPath, error);
                return {
                    resultType: files_1.FILE_UPLOAD_RESULT_TYPES.FAILURE,
                    error,
                    file,
                };
            }
        };
    }))
        .finally(() => {
        if (!convertFields)
            return;
        if (saveOutput) {
            fieldsJsObjects.forEach(fieldsJs => fieldsJs.saveOutput());
        }
        (0, handleFieldsJS_1.cleanupTmpDirSync)(tmpDir || '');
    });
    return results;
}
exports.uploadFolder = uploadFolder;
function hasUploadErrors(results) {
    return results.some(result => result.resultType === files_1.FILE_UPLOAD_RESULT_TYPES.FAILURE);
}
exports.hasUploadErrors = hasUploadErrors;
